package part8.interpreter;

import part8.ast.AST;
import part8.ast.BinOp;
import part8.ast.Num;
import part8.ast.UnaryOp;
import token.Token;

public class Parser {
    private final Lexer lexer;
    private Token currentToken;

    public Parser(Lexer lexer) throws Exception {
        this.lexer = lexer;
        this.currentToken = this.lexer.getNextToken();
    }

    private void error() throws Exception {
        throw new Exception("Invalid syntax");
    }

    private void eat(Token.TokenType tokenType) throws Exception {
        if (currentToken.type == tokenType){
            currentToken = lexer.getNextToken();
        }
        else {
            this.error();
        }
    }

    private AST factor() throws Exception {
        // factor : (PLUS | MINUS) factor | INTEGER | LPAREN expr RPAREN
        Token token = currentToken;
        if (token.type == Token.TokenType.PLUS || token.type == Token.TokenType.MINUS){
            if (token.type == Token.TokenType.PLUS){
                eat(Token.TokenType.PLUS);
            }
            else {
                eat(Token.TokenType.MINUS);
            }
            return new UnaryOp(token, factor());
        }
        else if (currentToken.type == Token.TokenType.INTEGER){
            eat(Token.TokenType.INTEGER);
            return new Num(token);
        }
        else {
            eat(Token.TokenType.LPAREN);
            AST result = expr();
            eat(Token.TokenType.RPAREN);
            return result;
        }
    }

    private AST term() throws Exception {
        // term : factor ((MUL | DIV) factor)*
        AST node = factor();
        while (currentToken.type == Token.TokenType.MUL || currentToken.type == Token.TokenType.DIV){
            Token token = currentToken;
            if (token.type == Token.TokenType.MUL){
                eat(Token.TokenType.MUL);
            }
            else {
                eat(Token.TokenType.DIV);
            }

            node = new BinOp(node, token, this.factor());
        }
        return node;
    }

    private AST expr() throws Exception {
        /*
        expr   : term ((PLUS | MINUS) term)*
        term   : factor ((MUL | DIV) factor)*
        factor : (PLUS | MINUS) factor | INTEGER | LPAREN expr RPAREN
         */
        AST node = term();

        while (currentToken.type == Token.TokenType.PLUS || currentToken.type == Token.TokenType.MINUS){
            Token token = currentToken;
            if (token.type == Token.TokenType.PLUS){
                eat(Token.TokenType.PLUS);
            }
            else {
                eat(Token.TokenType.MINUS);
            }
            node = new BinOp(node, token, term());
        }
        return node;
    }

    public AST parse() throws Exception {
        return this.expr();
    }
}
